package sicnu.cs.ich.security.config;


import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import sicnu.cs.ich.common.services.impl.RedisService;
import sicnu.cs.ich.security.exceptions.SimpleAccessDeniedHandler;
import sicnu.cs.ich.security.exceptions.SimpleAuthenticationEntryPoint;
import sicnu.cs.ich.security.security.jwt.JwtFilter;
import sicnu.cs.ich.security.security.provider.EmailAuthenticationProvider;
import sicnu.cs.ich.security.security.provider.WechatAuthenticationProvider;
import sicnu.cs.ich.security.security.rest.*;
import sicnu.cs.ich.security.util.JwtUtil;

import java.util.Arrays;
import java.util.Collections;

@Slf4j
@Configuration
@RequiredArgsConstructor
//@Import(SecurityProblemSupport.class)
public class BaseSecurityConfig extends WebSecurityConfigurerAdapter {

    // @Autowired
    //protected SecurityProblemSupport problemSupport;

    @Qualifier("userDetailServiceImpl")
    @Autowired
    protected UserDetailsService userDetailsService;
    @Autowired
    protected JwtFilter jwtFilter;
    @Autowired
    protected ObjectMapper objectMapper;
    @Autowired
    protected JwtUtil jwtUtil;
    @Autowired
    protected RedisService redisService;
    @Autowired
    protected DefaultMQProducer producer;
    @Autowired
    protected RestAuthenticationSuccessHandler restAuthenticationSuccessHandler;
    @Autowired
    protected RestAuthenticationFailureHandler restAuthenticationFailureHandler;
    @Autowired
    protected WechatAuthenticationProvider wechatAuthenticationProvider;
    // 微信登录路径
    public static final String WECHAT_LOGIN_PATH = "/wechatLogin";
    public static final String USER_FORM_LOGIN_PATH = "/login";
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .logout(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeRequests(authentication ->
                        authentication
                                .antMatchers("/", "/auth/**").permitAll()
                                .antMatchers("/druid/**").permitAll()

                                // Swagger 相关页面权限放行
                                .antMatchers("/v2/api-docs", "/swagger-resources/configuration/ui",
                                        "/swagger-resources", "/swagger-resources/configuration/security",
                                        "/swagger-ui.html", "/webjars/**").permitAll()
                        )
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(wechatAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)
                .addFilterAfter(emailAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling().authenticationEntryPoint(new SimpleAuthenticationEntryPoint())
                .accessDeniedHandler(new SimpleAccessDeniedHandler());
    }

    @Override
    public void configure(WebSecurity web) {
        web
                .ignoring()
                .antMatchers("/resources/**", "/static/**", "/public/**", "/h2/**")
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }
    @Bean
    @ConditionalOnMissingBean
    public RestAuthenticationFailureHandler restAuthenticationFailureHandler(){
        return new RestAuthenticationFailureHandler(objectMapper);
    }

    @Bean
    @ConditionalOnMissingBean
    public RestAuthenticationSuccessHandler restAuthenticationSuccessHandler(){
        return new RestAuthenticationSuccessHandler(jwtUtil, objectMapper, producer);
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
        // 配置微信Provider
        auth.authenticationProvider(wechatAuthenticationProvider);
        var provider = new EmailAuthenticationProvider(userDetailsService, redisService);
        auth.authenticationProvider(provider);
    }
    @Bean
    @ConditionalOnMissingBean
    public WechatAuthenticationFilter wechatAuthenticationFilter() throws Exception {
        // 配置微信登录
        WechatAuthenticationFilter wechatAuthenticationFilter =
                new WechatAuthenticationFilter(WECHAT_LOGIN_PATH);
        wechatAuthenticationFilter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
        wechatAuthenticationFilter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
        wechatAuthenticationFilter.setAuthenticationManager(authenticationManager());
        return wechatAuthenticationFilter;
    }

    @Bean
    @ConditionalOnMissingBean
    public EmailAuthenticationFilter emailAuthenticationFilter() throws Exception {
        var filter = new EmailAuthenticationFilter(objectMapper);
        filter.setAuthenticationManager(authenticationManager());
        filter.setAuthenticationSuccessHandler(new RestAuthenticationSuccessHandler(jwtUtil, objectMapper, producer));
        filter.setAuthenticationFailureHandler(new RestAuthenticationFailureHandler(objectMapper));
        filter.setFilterProcessesUrl("/email/login");
        return filter;
    }


    @Bean
    @ConditionalOnMissingBean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    @ConditionalOnMissingBean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }
    @Bean
    @ConditionalOnMissingBean
    public RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        val filter = new RestAuthenticationFilter(redisService, objectMapper);
        filter.setAuthenticationSuccessHandler(restAuthenticationSuccessHandler);
        filter.setAuthenticationFailureHandler(restAuthenticationFailureHandler);
        filter.setAuthenticationManager(authenticationManager());
        filter.setFilterProcessesUrl(USER_FORM_LOGIN_PATH);
        return filter;
    }

    @Bean
    @ConditionalOnMissingBean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        configuration.addExposedHeader("X-Authenticate");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

}
